Design System — v1.0
The source of truth for every design token used on this portfolio. Colors, typography, spacing, radius, motion — all documented here as they ship in production. Covers desktop, tablet, and mobile behavior.
All previews are live CSS — what you see on this page is what the site uses.
01 — Colors
11 semantic color tokens. Every color on the site belongs to one of these groups — surface, text, brand, border, or semantic state. No hardcoded hex values in page CSS.
Each token has a single purpose. Names describe intent ("text-secondary"), not position ("muted2"). Backgrounds collapsed to 3 levels that actually differ visually.
Surface — 3 levels
Text — 3 tiers of emphasis
Brand — accent only
Borders — 2 levels
Semantic states
02 — Typography
Two font families (Outfit for headings, Inter for body) on a 1.25× modular scale. Every heading and body size is one of the 9 tokens below. Scales automatically across desktop, tablet, and mobile.
Outfit handles all headings. Inter handles all body. 9 sizes cover every use case, each built on the perfect-fourth modular scale (1.25×).
Responsive scale per breakpoint
| Token | Desktop | Tablet | Mobile |
|---|---|---|---|
| Display | 72px | 56px | 40px |
| H1 | 56px | 44px | 32px |
| H2 | 44px | 36px | 28px |
| H3 | 32px | 28px | 24px |
| H4 | 22px | 20px | 18px |
| Body L | 18px | 17px | 16px |
| Body | 16px | 16px | 15px |
| Small | 14px | 14px | 13px |
| Eyebrow | 12px | 12px | 11px |
03 — Border Radius
Sharp corners everywhere. Rounded rectangles dilute the editorial tone, so every button, card, input, chip, and panel uses 0 radius. The only exception: inherently circular shapes (avatars, swatches, dot indicators).
No rounded rectangles anywhere — buttons, cards, inputs, chips, modals all use 0. The only radius allowed is --radius-full (999px) for inherently circular shapes (avatars, color swatches, dot indicators).
Usage mapping
| Token | Value | Where |
|---|---|---|
| --radius-none | 0 | Everything — buttons, cards, inputs, images, modals, filter chips, badges, section panels |
| --radius-full | 999px | Avatars, color swatches, dot indicators (only inherently round shapes) |
04 — Spacing
Every spacing value should belong to a predictable scale. The 8-point grid is the industry standard and works here because it aligns cleanly with the type scale.
Named scale from xs to 5xl. Use tokens, not hardcoded pixels. Scales automatically across breakpoints.
Section padding per breakpoint
| Type | Desktop | Tablet | Mobile |
|---|---|---|---|
| Horizontal (--pad) | 96px | 48px | 24px |
| Section vertical | 96px | 64px | 48px |
| Compact section | 64px | 48px | 32px |
| Card padding | 24px | 20px | 16px |
| Button padding | 12px 24px | 12px 20px | 10px 16px |
05 — Buttons
One CTA pattern across the entire site: uppercase text with a trailing arrow. Hover triggers the per-letter flip (white → accent yellow) with staggered delay. No backgrounds, no borders. Filter chips are the only exception (needed for group-toggle UX).
One CTA pattern across the site: uppercase text with a trailing arrow. Hover triggers the per-letter flip (white → accent yellow) with staggered delay. No backgrounds, no borders, no pill-vs-solid debates.
CTA specs across breakpoints
| Property | Desktop | Tablet | Mobile |
|---|---|---|---|
| Font size | 15px | 15px | 14px |
| Letter spacing | .5px | .5px | .5px |
| Arrow spacing (rest) | 10px | 10px | 8px |
| Arrow spacing (hover) | 14px | 14px | 12px |
| Letter flip duration | 250ms | 250ms | 250ms |
| Letter stagger | 20ms/char | 20ms/char | 20ms/char |
06 — Forms
Inputs should feel like part of the same family as buttons — same radius, same padding math, same focus ring.
Inputs match the rest of the system: sharp corners, subtle border. Focus turns the border accent yellow. Error state uses the --state-error token with a faint background tint.
Enter a valid email address
--state-error + 4% background tint for soft context.
07 — Icon Buttons
Icon buttons are used for navigation (carousel arrows, lightbox prev/next) and utility actions (close, menu). They need their own consistent style because text-only CTAs don't work when there's no text.
Every icon button shares the exact same shell. Two sizes (default 56×56, small 44×44). Same border, same hover state, same active press.
Default — 56×56 (carousel, lightbox arrows)
Small — 44×44 (mobile arrows, compact UI)
Shared specs
| Property | Default | Small |
|---|---|---|
| Size | 56×56 | 44×44 |
| Border | 2px solid #323936 | 2px solid #323936 |
| Background | transparent | transparent |
| Icon size | 18×18 | 14×14 |
| Stroke width | 2px | 2px |
| Hover | border-color → --accent | border-color → --accent |
| Active | border-width 1px + scale(.96) | border-width 1px + scale(.96) |
| Focus-visible | outline 2px --accent · offset 2px | outline 2px --accent · offset 2px |
Used for
| Context | Size | Where |
|---|---|---|
| Testimonial carousel | 56px | Homepage testimonials prev/next |
| Lightbox arrows | 56px (44px mobile) | Case study image zoom prev/next |
| Lightbox close | 48px | Case study image zoom top-right |
| Burger menu | 44px | Mobile navbar |
08 — Cards
One card pattern for every context — blog posts, case studies, project grid, metrics, testimonials, value props. Sharp corners, subtle border, consistent hover behavior (lift + thumbnail zoom + title shifts to accent).
Every card on the site shares the same shell: sharp corners, subtle border, thumbnail on top, content below. Used by blog posts, case studies, value props, testimonials, metrics — all the same.
Project / Blog
Image + title + description. Hover lifts 4px, thumbnail zooms 1.04x, title shifts to accent.
Metric / Value
Same shell, text-only content. Used for stats, testimonials, value propositions.
Shared behavior
| Attribute | Value |
|---|---|
| Border radius | 0 |
| Background | --surface-raised |
| Border | 1px solid --border-subtle |
| Padding (desktop) | 24px |
| Padding (tablet) | 20px |
| Padding (mobile) | 16px |
| Hover lift | translateY(-4px) · 350ms · --ease-out |
| Image zoom | scale(1.04) · 500ms · --ease-out |
| Title on hover | color → --accent · 250ms |
09 — Breakpoints
Three breakpoints covering every device — Mobile (< 640px), Tablet (640–1024px), Desktop (> 1024px). Every token scales predictably at these three tiers.
Three clear tiers aligned to actual devices.
Mobile
< 640px
Single column, stacked nav hidden behind hamburger, 24px side padding, tightest type scale.
Tablet
640 – 1024px
2-column grids where possible, nav still hamburger, 48px side padding, medium type scale.
Desktop
> 1024px
Full multi-column layouts, horizontal nav visible, 96px side padding, full type scale.
Example — Hero heading scale
Mobile
< 640px
I Design Solutions.
Hello there, I am Shreyansh Gaurav.
Tablet
640 – 1024px
I Design Solutions.
Hello there, I am Shreyansh Gaurav.
Desktop
> 1024px
I Design Solutions.
Hello there, I am Shreyansh Gaurav.
min-width) make intent clear. Less CSS overall, predictable behavior on every device.
10 — Motion
Motion is part of the brand. 5 duration tokens + 4 easing tokens power every animation on the site — from hover states to scroll-triggered reveals.
Every animation picks from a short list. One token per intent (hover, reveal, long-form, marquee).
Duration tokens
| Token | Value | Use |
|---|---|---|
| --duration-instant | 100ms | Button press, focus ring |
| --duration-fast | 200ms | Hover states (color, border, shadow) |
| --duration-base | 350ms | Card lift, image zoom, nav slide |
| --duration-slow | 600ms | Heading reveals (gravity drop) |
| --duration-longform | 1800ms | Body per-line wave reveal |
Easing tokens
--ease-out
cubic-bezier(.22, 1, .36, 1)
Default. Reveals, hovers, anything entering view.
--ease-smooth
cubic-bezier(.19, 1, .22, 1)
Long-form body reveals. Gentlest exit curve.
--ease-spring
cubic-bezier(.34, 1.56, .64, 1)
Playful overshoot. Use sparingly (1–2 moments per page).
--ease-linear
linear
Marquees, spinners, infinite loops only.
The five animations we use sitewide, documented and demoed.
Applied to every yellow primary heading across the site (homepage sections, case study titles, blog post titles, contact, about). Letters drop in from above with a subtle bounce.
Live demo
| Property | Value |
|---|---|
| Duration | 600ms (--duration-slow) |
| Easing | cubic-bezier(.22, 1, .36, 1) + overshoot keyframes |
| Stagger | 50ms per character |
| Trigger | IntersectionObserver (threshold 0.15) — runs once |
| Reduced-motion | Respected — animation skipped |
Main body paragraphs across the site (hero descriptions, section intros, case study overviews, contact subtitle). Each line emerges from a clipped mask, offset from below.
Live demo
I design digital products that make complex things feel simple. My work sits at the intersection of research, craft, and systems thinking — built on empathy for real users.
| Property | Value |
|---|---|
| Duration | 1800ms (--duration-longform) |
| Easing | --ease-smooth (cubic-bezier(.19, 1, .22, 1)) |
| Stagger | 0ms — all lines move together |
| Opacity | Never changes — lines stay 100% visible behind mask |
| Trigger | IntersectionObserver (threshold 0.15) |
Applied to every link, button, and CTA across the site — navbar items, "View All", "View Case Study", "Send Message", footer email, filter chips. Per-letter, not per-word — each character is wrapped in its own mask and flips individually from white to accent yellow, with a staggered delay that creates the wave feel.
Hover me
| Property | Value |
|---|---|
| Unit of animation | 1 character (not word) |
| At-rest transition | 1300ms · --ease-out |
| On-hover transition | 250ms · --ease-out |
| Per-letter stagger | 20ms, capped at 400ms total |
| Color | --text-primary → --accent |
| Reveal mechanic | Y-axis flip — original char slides up, accent clone slides up from below |
| Applied via | split-hover.js + base.css .split-hover rules |
Blog cards, project cards, Next Project block. Card lifts 4px, thumbnail zooms 1.04x, title turns accent.
Hover me
| Element | Value |
|---|---|
| Card lift | translateY(-4px) · 350ms · --ease-out |
| Shadow | 12px 40px · 350ms |
| Thumbnail zoom | scale(1.04) · 500ms · --ease-out |
| Title color | 200ms · ease |
Horizontal scrolling strips (client logos, decorative background text). Linear loop, pauses on hover.
| Property | Value |
|---|---|
| Duration | 28s (logos) / 32s (bgline) |
| Easing | --ease-linear (the only linear use) |
| Direction | Alternating (logos-top right, logos-bottom left) |
| Pause on hover | animation-play-state: paused |
--duration-fast for hover (color, border, shadow).--duration-base for movement (transform, slide).--duration-slow for entry reveals (heading gravity drop).--duration-longform for per-line body wave reveals only.prefers-reduced-motion — skip all non-essential animation.IntersectionObserver with threshold 0.15, and unobserve after firing once.Design System v1 · Draft for review · Back to site